//#define WMB_DEBUG_RECURSIVE_CELL_VALUE_NEEDED
//#define WMB_DEBUG_DECREASE_ROWCOUNT

using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Reflection;
using System.Threading;
using System.Windows.Forms;
using Timer=System.Threading.Timer;

namespace Mitosoft.Definitions.Controls
{
  /// <summary>
  /// Represents an enhanced data grid view control.
  /// </summary>
  public class DataGridViewControl : DataGridView
  {
    #region Constants

    /// <summary>
    /// Represents the minimum height we expect a row must have
    /// so we can assume that this row can be viewed by the user.
    /// This value is used to calculate the estimated maximum number 
    /// of rows which can be displayed in the grid.
    /// </summary>
    public const int MINIMUM_ROW_HEIGHT = 2;

    /// <summary>
    /// Represents the interval to start the timer controlling decreasing
    /// the row count of this grid.
    /// </summary>
    public const int TIMER_TO_DECREASE_ROW_COUNT_INTERVAL_IN_MS = 25;
    
    #endregion

    #region Delegates and events

    /// <summary>
    /// Represents the method that will handle setting the value of a property.
    /// </summary>
    /// <param name="obj">Object of which we want to set a property value.</param>
    /// <param name="propertyName">Name of the property we want to set the value</param>
    /// <param name="index">Not used, always null</param>
    protected delegate void SetPropertyValueDelegate(Object obj, Object propertyName, Object[] index);
    
    /// <summary>
    /// Represents the method that will handle the <see cref="RowIsEmptyInformationNeeded"/> event.
    /// </summary>
    /// <param name="sender">The source of the event.</param>
    /// <param name="rowIndex">The index of the row from which we want to know if it is empty.</param>
    /// <returns>True if the given row is empty.</returns>
    public delegate bool DataGridViewRowIsEmptyEventHandler(object sender, int rowIndex);

    /// <summary>
    /// Occurs when the data grid wants to know if a given row is empty.
    /// If the given row is empty and if <see cref="RemoveEmptyRows"/> is set to true,
    /// the number of rows of this grid will be decreased by one.
    /// </summary>
    public event DataGridViewRowIsEmptyEventHandler RowIsEmptyInformationNeeded;

    /// <summary>
    /// Represents the method that will handle the <see cref="RowShouldBeVisibleInformationNeeded"/> event.
    /// </summary>
    /// <param name="sender">The source of the event</param>
    /// <param name="rowIndex">The index of the row from which we want to know if it should be visible.</param>
    /// <returns>True if the given row should be visible.</returns>
    public delegate bool DataGridViewRowShouldBeVisibleEventHandler(object sender, int rowIndex);

    /// <summary>
    /// Occurs when the data grid wants to know if a given row should be visible.
    /// </summary>
    public event DataGridViewRowShouldBeVisibleEventHandler RowShouldBeVisibleInformationNeeded;
    
    #endregion

    #region Fields

    protected bool _multipleCallingOfCellValueNeededEventIsPossible = true;
    protected bool _cellValueNeededIsActive;
    protected Timer  _timerToDecreaseRowCount;
    protected Queue _decreasingRowCountQueue;
    private readonly object _lockDecreasingHelper = new object();
    protected int _rowIndexToAddToTheDecreasingRowCountQueue;
    protected bool _removeEmptyRows;
    protected bool _cellValueNeededIsBlocked;
    protected bool _setRowVisibilityDuringRequestingACellValue;

    #endregion

    #region Constructor(s)

    #endregion

    #region Properties
    
    /// <summary>
    /// Returns a collection of data grid view rows which are selected
    /// and sorted by the index. The index represents also the row number
    /// in the data grid view.
    /// </summary>
    public List<DataGridViewRow> SelectedRowsSortedByIndex
    {
      get
      {
        if (this.SelectedRows == null) return null;
        if (this.SelectedRows.Count <= 0) return null;

        List<DataGridViewRow> tempSelectedRows = new List<DataGridViewRow>();
        foreach (DataGridViewRow gridViewRow in this.SelectedRows)
          tempSelectedRows.Add(gridViewRow);

        SortedList tempResultingSelectedRowsSortedByIndex = 
          this.SortDataGridViewRowCollectionByIndex(tempSelectedRows);

        List<DataGridViewRow> resultingSelectedRowsSortedByIndex =
          new List<DataGridViewRow>();

        foreach (DictionaryEntry entry in tempResultingSelectedRowsSortedByIndex)
          resultingSelectedRowsSortedByIndex.Add(entry.Value as DataGridViewRow);

        return resultingSelectedRowsSortedByIndex;
      }
    }

    /// <summary>
    /// Gets or sets whether the event responsible for calling
    /// cell values should be blocked.
    /// </summary>
    protected bool CellValueNeededIsBlocked
    {
      get { return _cellValueNeededIsBlocked; }
      set { _cellValueNeededIsBlocked = value; }
    }

    /// <summary>
    /// Gets or sets whether empty rows should be removed from the grid by
    /// decreasing the number of rows of this grid.
    /// </summary>
    public bool RemoveEmptyRows
    {
      get { return _removeEmptyRows; }
      set { _removeEmptyRows = value; }
    }

    /// <summary>
    /// Represents the row index which should be added to the queue.
    /// </summary>
    protected int RowIndexToAddToTheDecreasingRowCountQueue
    {
      get { return _rowIndexToAddToTheDecreasingRowCountQueue; }
      set { _rowIndexToAddToTheDecreasingRowCountQueue = value; }
    }

    /// <summary>
    /// Represents the queue containing the row indexes because
    /// of which we want to decrease the row count.
    /// </summary>
    protected Queue DecreasingRowCountQueue
    {
      get
      {
        if (_decreasingRowCountQueue == null)
          _decreasingRowCountQueue = new Queue();
        return _decreasingRowCountQueue;
      }
      set { _decreasingRowCountQueue = value; }
    }

    /// <summary>
    /// Represents the timer used to decrease the row number.
    /// </summary>
    [Browsable(false)]
    protected Timer TimerToDecreaseRowCount
    {
      get
      {
        if (_timerToDecreaseRowCount == null)
        {
          this._timerToDecreaseRowCount = 
            new Timer(this.TimerToDecreaseRowCount_Tick);
          this._timerToDecreaseRowCount.Change(Timeout.Infinite, Timeout.Infinite);
        }

        return _timerToDecreaseRowCount;
      }
      set { _timerToDecreaseRowCount = value; }
    }

    /// <summary>
    /// Gets or sets whether multiple calling of the cell value needed event should be possible.
    /// Default value is true.
    /// </summary>
    public bool MultipleCallingOfCellValueNeededEventIsPossible
    {
      get { return _multipleCallingOfCellValueNeededEventIsPossible; }
      set { _multipleCallingOfCellValueNeededEventIsPossible = value; }
    }

    /// <summary>
    /// Represents whether the event where a cell value is 
    /// needed to fill the grid containing the log line contents,
    /// is just working (active).
    /// </summary>
    protected bool CellValueNeededIsActive
    {
      get { return _cellValueNeededIsActive; }
      set { _cellValueNeededIsActive = value; }
    }

    /// <summary>
    /// Represents the estimated maximum number of rows which
    /// can be displayed in the grid.
    /// </summary>
    [Browsable(false)]
    public int EstimatedMaximumDisplayedRowCountOfLogLines
    {
      get
      {
        return this.ClientSize.Height / MINIMUM_ROW_HEIGHT + 1;
      }
    }
    
    #endregion

    #region Methods

    /// <summary>
    /// Sorts the given data grid view row collection by the index of the rows which
    /// are also the row numbers inside the data grid view.
    /// The first row in the collection will contain the smallest index.
    /// </summary>
    /// <param name="dataGridViewRowCollection">Collection containing the data grid view rows to be sorted.</param>
    /// <returns>Sorted collection with cloned rows.</returns>
    protected SortedList SortDataGridViewRowCollectionByIndex(List<DataGridViewRow> dataGridViewRowCollection)
    {
      if (dataGridViewRowCollection == null) return null;
      if (dataGridViewRowCollection.Count <= 0) return null;

      SortedList resultingSortedDataGridViewRowCollection = new SortedList();
      foreach (DataGridViewRow dataGridViewRow in dataGridViewRowCollection)
        resultingSortedDataGridViewRowCollection.Add(dataGridViewRow.Index, dataGridViewRow);

      return resultingSortedDataGridViewRowCollection;
    }

    /// <summary>
    /// Gets or sets whether the data grid asks if the row, from which a value
    /// is requested, is visible. If you handle large data and many invisible
    /// rows, dont set this value to true because otherwise the data grid will
    /// ask until visible rows are available to show. 
    /// Default value is false.
    /// </summary>
    public bool SetRowVisibilityDuringRequestingACellValue
    {
      get { return _setRowVisibilityDuringRequestingACellValue; }
      set { _setRowVisibilityDuringRequestingACellValue = value; }
    }

    /// <summary>
    /// Used to set the property value of the given control via a delegate.
    /// This is required when another thread wants to set the property of a control.
    /// </summary>
    /// <param name="control">Control of which we want to set a property value</param>
    /// <param name="propertyName">Name of the property.</param>
    /// <param name="propertyValue">Value we want to set.</param>
    protected void SetValueOfControlProperty(Control control, String propertyName, Object propertyValue)
    {
      PropertyInfo propertyInfo = control.GetType().GetProperty(propertyName);
      Delegate delegateSetValue = new SetPropertyValueDelegate(propertyInfo.SetValue);
      // ReSharper disable RedundantExplicitArraySize
      control.Invoke(delegateSetValue, new object[3] {control, propertyValue, null});
      // ReSharper restore RedundantExplicitArraySize
    }

    ///<summary>
    ///Raises the <see cref="E:System.Windows.Forms.DataGridView.CellValueNeeded"></see> event.
    ///<seealso cref="MultipleCallingOfCellValueNeededEventIsPossible"/>
    ///</summary>
    ///
    ///<param name="e">A <see cref="T:System.Windows.Forms.DataGridViewCellValueEventArgs"></see> that contains the event data. </param>
    ///<exception cref="T:System.ArgumentOutOfRangeException">The value of the <see cref="P:System.Windows.Forms.DataGridViewCellValueEventArgs.ColumnIndex"/> property of e is less than zero or greater than the number of columns in the control minus one.-or-The value of the <see cref="P:System.Windows.Forms.DataGridViewCellValueEventArgs.RowIndex"></see> property of e is less than zero or greater than the number of rows in the control minus one.</exception>
    protected override void OnCellValueNeeded(DataGridViewCellValueEventArgs e)
    {
      if (this.CellValueNeededIsBlocked) return;
      
      // Prevent recursive calling of this event:
      if (!this.MultipleCallingOfCellValueNeededEventIsPossible && this.CellValueNeededIsActive)
      {
        #if WMB_DEBUG_RECURSIVE_CELL_VALUE_NEEDED
          Debug.Print("DataGridViewControl: Exit OnCellValueNeeded because it is already started.");
        #endif
        return;
      }
      
      this.CellValueNeededIsActive = true;

      try
      {
        #if WMB_DEBUG_RECURSIVE_CELL_VALUE_NEEDED
          Debug.Print("DataGridViewControl: Cell value needed Row = {0} Column = {1}", e.RowIndex, e.ColumnIndex);
        #endif
        
        base.OnCellValueNeeded(e);
        
        // Decrease the number of rows if empty rows should be removed and the given row is empty:
        if (this.RemoveEmptyRows && this.RowIsEmptyInformationNeeded != null && this.RowIsEmptyInformationNeeded(this, e.RowIndex))
          this.DecreaseRowCount(e.RowIndex);

        if (this.SetRowVisibilityDuringRequestingACellValue && this.RowShouldBeVisibleInformationNeeded != null)
          this.Rows[e.RowIndex].Visible = this.RowShouldBeVisibleInformationNeeded(this, e.RowIndex);
      }
      finally
      {
        this.CellValueNeededIsActive = false;
      }
    }

    /// <summary>
    /// Listens to the timer which is used to decrease the number of rows of this grid.
    /// </summary>
    /// <param name="state">Not used</param>
    private void TimerToDecreaseRowCount_Tick(object state)
    {
      Thread thread = new Thread(this.EvaluateDecreaseRowCountQueue);
      thread.Start();
    }
    
    /// <summary>
    /// Evaluates the queue containing the values used to decrease the row count of this grid.
    /// </summary>
    protected void EvaluateDecreaseRowCountQueue()
    {
      lock(this._lockDecreasingHelper) {
      
        #if WMB_DEBUG_DECREASE_ROWCOUNT
          System.DateTime now = System.DateTime.Now;
        #endif

        this.TimerToDecreaseRowCount.Change(Timeout.Infinite, Timeout.Infinite);

        // Exit if nothing to do:
        if (this.DecreasingRowCountQueue == null || this.DecreasingRowCountQueue.Count == 0)
        {
          #if WMB_DEBUG_DECREASE_ROWCOUNT
            Debug.Print(
              "{0} {1} {2} TimerToDecreaseRowCount_Tick: Nothing to do because queue is empty.",
              now.Minute, now.Second, now.Millisecond);
          #endif
          return;
        }

        int nextRowIndexInQueue = (int)this.DecreasingRowCountQueue.Dequeue();

        // Exit if the next row index is too big:
        if (nextRowIndexInQueue >= this.RowCount)
        {
          #if WMB_DEBUG_DECREASE_ROWCOUNT
            Debug.Print(
              "{0} {1} {2} TimerToDecreaseRowCount_Tick: Row index {3} >= Row count {4}",
              now.Minute, now.Second, now.Millisecond, nextRowIndexInQueue, this.RowCount);
          #endif

          this.EnableTimerToDecreaseRowCountInDependencyOfQueue();
          return;
        }
        
        if (this.InvokeRequired)
          this.SetValueOfControlProperty(this, "RowCount", this.RowCount-1);
        else
          this.RowCount--;

        #if WMB_DEBUG_DECREASE_ROWCOUNT
          Debug.Print(
            "{0} {1} {2} TimerToDecreaseRowCount_Tick: Row count decreased ({3}) Row index = {4}",
            now.Minute, now.Second, now.Millisecond, this.RowCount, nextRowIndexInQueue);
        #endif

        this.EnableTimerToDecreaseRowCountInDependencyOfQueue();
      }
    }
    
    /// <summary>
    /// Helper funktion: Enables the timer if the queue is not empty.
    /// </summary>
    protected void EnableTimerToDecreaseRowCountInDependencyOfQueue()
    {
      if (this.DecreasingRowCountQueue.Count <= 0) return;
      
      #if WMB_DEBUG_DECREASE_ROWCOUNT
        System.DateTime now = System.DateTime.Now;
      #endif

      this.TimerToDecreaseRowCount.Change(TIMER_TO_DECREASE_ROW_COUNT_INTERVAL_IN_MS, Timeout.Infinite);
      #if WMB_DEBUG_DECREASE_ROWCOUNT
        Debug.Print(
          "{0} {1} {2} TimerToDecreaseRowCount_Tick: Timer enabled again.",
          now.Minute, now.Second, now.Millisecond);
      #endif
    }
    
    /// <summary>
    /// Decreases the number of rows by one.
    /// </summary>
    /// <param name="rowIndex">Information why the row number should be decreased.</param>
    public void DecreaseRowCount(int rowIndex)
    {
      this.RowIndexToAddToTheDecreasingRowCountQueue = rowIndex;
      Thread thread = new Thread(this.AddRowIndexToTheDecreasingRowCountQueue);
      thread.Start();
    }
    
    /// <summary>
    /// Adds the row index, stored in the RowIndexToAddToTheDecreasingRowCountQueue property,
    /// to the queue used to decrease the row count of this grid.
    /// </summary>
    protected void AddRowIndexToTheDecreasingRowCountQueue() 
    {
      lock(this._lockDecreasingHelper) {

        // It is important to enable here anyway the timer!
        this.TimerToDecreaseRowCount.Change(TIMER_TO_DECREASE_ROW_COUNT_INTERVAL_IN_MS, Timeout.Infinite);
        
        #if WMB_DEBUG_DECREASE_ROWCOUNT
          System.DateTime now = System.DateTime.Now;
        #endif

        if (this.DecreasingRowCountQueue.Contains(this.RowIndexToAddToTheDecreasingRowCountQueue))
        {
          #if WMB_DEBUG_DECREASE_ROWCOUNT
            Debug.Print(
              "{0} {1} {2} DecreasingRowCountQueue: Row index {3} not added because it already exists.",
              now.Minute, now.Second, now.Millisecond, this.RowIndexToAddToTheDecreasingRowCountQueue);
          #endif
          return;
        }

        if (this.RowIndexToAddToTheDecreasingRowCountQueue >= this.RowCount)
        {
          #if WMB_DEBUG_DECREASE_ROWCOUNT
            Debug.Print(
              "{0} {1} {2} DecreasingRowCountQueue: Row index {3} not added because it is bigger than row count.",
              now.Minute, now.Second, now.Millisecond, this.RowIndexToAddToTheDecreasingRowCountQueue);
          #endif
          return;
        }

        this.DecreasingRowCountQueue.Enqueue(this.RowIndexToAddToTheDecreasingRowCountQueue);

        #if WMB_DEBUG_DECREASE_ROWCOUNT
          Debug.Print(
            "{0} {1} {2} DecreasingRowCountQueue: Row index {3} added!", 
            now.Minute, now.Second, now.Millisecond,
            this.RowIndexToAddToTheDecreasingRowCountQueue);
        #endif
      }
    }
    
    /// <summary>
    /// Returns the next row which should be visible.
    /// If no next row was found, a previous row will be returned.
    /// <seealso cref="FindNextRowWithGivenShouldBeVisibleValue"/> and
    /// <seealso cref="FindPreviousRowWithGivenShouldBeVisibleValue"/>
    /// </summary>
    /// <param name="startingRowIndex">Starting row for the search.</param>
    /// <param name="visible">Represents the visible value we are looking for.</param>
    /// <returns>Found row index or null if nothing was found.</returns>
    protected int? FindNextOrPreviousRowWithGivenShouldBeVisibleValue(int? startingRowIndex, bool visible)
    {
      int? foundRowIndex = this.FindNextRowWithGivenShouldBeVisibleValue(startingRowIndex, visible);
      if (foundRowIndex != null) return foundRowIndex;

      return this.FindPreviousRowWithGivenShouldBeVisibleValue(startingRowIndex, visible);
    }
    
    /// <summary>
    /// Returns the next row which should be visible, starting at the
    /// given row. The given row will be included in the search.
    /// So if the given row should be visible, the same row index will
    /// be returned.
    /// If the given row index is null, the search will begin at the beginning.
    /// </summary>
    /// <param name="startingRowIndex">Starting row for the search.</param>
    /// <param name="visible">Represents the visible value we are looking for</param>
    /// <returns>Found row index or null if nothing was found.</returns>
    protected int? FindNextRowWithGivenShouldBeVisibleValue(int? startingRowIndex, bool visible)
    {
      // Exit if no rows are available:
      if (this.RowCount <= 0) return null;
      
      // Exit if there is no handler which will deliver informations:
      if (this.RowShouldBeVisibleInformationNeeded == null) return null;

      if (startingRowIndex == null) startingRowIndex = 0;
      startingRowIndex = this.CorrectedRowIndex((int)startingRowIndex);
      
      for(int currentRowIndex = (int)startingRowIndex; currentRowIndex < this.RowCount; currentRowIndex++)
        if (this.RowShouldBeVisibleInformationNeeded(this, currentRowIndex) == visible) return currentRowIndex;
      
      return null;  // Nothing found
    }
    
    /// <summary>
    /// Returns the previous row which should be visible, starting at the
    /// given row. The given row will be included in the search.
    /// So if the given row should be visible, the same row index will
    /// be returned.
    /// If the given row index is null, the search will begin at the end.
    /// </summary>
    /// <param name="startingRowIndex">Starting row for the search.</param>
    /// <param name="visible">Represents the visible value we are looking for.</param>
    /// <returns>Found row index or null if nothing was found.</returns>
    protected int? FindPreviousRowWithGivenShouldBeVisibleValue(int? startingRowIndex, bool visible)
    {
      // Exit if no rows are available:
      if (this.RowCount <= 0) return null;

      // Exit if there is no handler which will deliver informations:
      if (this.RowShouldBeVisibleInformationNeeded == null) return null;

      if (startingRowIndex == null) startingRowIndex = this.RowCount-1;
      startingRowIndex = this.CorrectedRowIndex((int)startingRowIndex);

      for (int currentRowIndex = (int)startingRowIndex; currentRowIndex >= 0; currentRowIndex--)
        if (this.RowShouldBeVisibleInformationNeeded(this, currentRowIndex) == visible)
          return currentRowIndex;
      
      return null;  // Nothing found
    }
    
    /// <summary>
    /// Returns the corrected row index:
    /// If the given row index is smaller then zero, zero will be returned.
    /// If the given row index is greater then RowCount minus 1, RowCount -1 
    /// will be returned.
    /// </summary>
    /// <param name="rowIndexToCorrect">Row index to be possibly corrected</param>
    /// <returns>Corrected value.</returns>
    protected int CorrectedRowIndex(int rowIndexToCorrect)
    {
      if (rowIndexToCorrect < 0) rowIndexToCorrect = 0;
      if (rowIndexToCorrect >= this.RowCount) rowIndexToCorrect = this.RowCount - 1;
      return rowIndexToCorrect;
    }
    
    /// <summary>
    /// Sets the visible property of the given row range to the given value.
    /// </summary>
    /// <param name="startingRowIndex">Index representing the starting row of the range.
    /// If null, the starting row index will be set to zero.</param>
    /// <param name="endRowIndex">Index representing the end row of the range.
    /// If null, the end row index will be set to RowCount minus one.</param>
    /// <param name="visible">False to make the given row range invisible.
    /// True to make the given range visible.
    /// Null to retrieve the information.</param>
    protected void SetRowsVisibleValue(int? startingRowIndex, int? endRowIndex, bool? visible)
    {
      // Exit if no rows are available:
      if (this.RowCount <= 0) return;
      
      // Exit if visible is null and there is no handler which will deliver informations:
      if (visible == null && this.RowShouldBeVisibleInformationNeeded == null) return;

      if (startingRowIndex == null) startingRowIndex = 0;
      if (endRowIndex == null) endRowIndex = this.RowCount - 1;

      try
      {
        this.CellValueNeededIsBlocked = true;
        
        for (int currentRowIndex = (int)startingRowIndex; currentRowIndex <= (int)endRowIndex; currentRowIndex++)
          this.Rows[currentRowIndex].Visible =
            visible == null ?
              this.RowShouldBeVisibleInformationNeeded(this, currentRowIndex) :
              (bool)visible;
      }
      finally 
      {
        this.CellValueNeededIsBlocked = false;
      }
    }

    /// <summary>
    /// Searches for the next or previous row with the given visible value 
    /// and displays it as the first row.
    /// </summary>
    public void ShowRowWithGivenVisibleValue(bool visible)
    {
      // Exit if no cells are displayed:
      if (this.FirstDisplayedCell == null) return;

      try
      {
        this.CellValueNeededIsBlocked = true;
        
        // Save the row index of the first displayed cell:
        int rowIndexOfFirstDisplayedRowCell = this.FirstDisplayedCell.RowIndex;

        // Search for the next or the previous row which should be visible:
        int? foundIndexOfRowWhichShouldBeVisible =
          this.FindNextOrPreviousRowWithGivenShouldBeVisibleValue(
            rowIndexOfFirstDisplayedRowCell, visible);

        // Special case: If no row was found, it means that all rows should be invisible
        if (foundIndexOfRowWhichShouldBeVisible == null) return;

        // Set the line we want to be displayed first:
        this.FirstDisplayedScrollingRowIndex = (int)foundIndexOfRowWhichShouldBeVisible;
      }
      finally 
      {
        this.CellValueNeededIsBlocked = false;
      }
    }
    
    /// <summary>
    /// Deselect all selected rows.
    /// </summary>
    public void DeselectAllRows()
    {
      try
      {
        if (this.SelectedRows == null || this.SelectedRows.Count <= 0)
          return;

        foreach (DataGridViewRow dataGridViewRow in this.SelectedRows)
          dataGridViewRow.Selected = false;
      }
      // ReSharper disable EmptyGeneralCatchClause
      catch
      // ReSharper restore EmptyGeneralCatchClause
      {
        // Intentionally left blank
      }
    }

    /// <summary>
    /// Selects the given lines.
    /// </summary>
    /// <param name="indexOfLines">Array containing the index numbers of the lines to be selected</param>
    public void SelectLines(List<ulong> indexOfLines)
    {
      try
      {
        this.DeselectAllRows();

        if (indexOfLines == null || indexOfLines.Count <= 0)
          return;

        foreach (int indexOfLogLineContent in indexOfLines)
          this.Rows[indexOfLogLineContent].Selected = true;

        if (!this.Rows[(int)indexOfLines[0]].Displayed)
          this.FirstDisplayedScrollingRowIndex = (int)indexOfLines[0];
      }
      // ReSharper disable EmptyGeneralCatchClause
      catch
      // ReSharper restore EmptyGeneralCatchClause
      {
        // Intentionally left blank
      }
    }

    #endregion
  }
}
